package com.minibobbo.utilities.spe;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.minibobbo.utilities.spe.effectmodules.SpeEffectModule;

public class SimplePlatformEngine {

	private enum Type {
		TiledMap
	}

	// Update time variables
	private double elapsedTime;
	private double lastTime;
	private int drawsSkipped;
	private int maxDrawsSkipped;
	private int targetFPS = 30;
	private int targetUPS = 30;

	private int collisionIteration = 1;

	private OrthographicCamera cam;
	private OrthographicCamera hudCam;
	private SpeMap currentMap;

	// Voxel variables
	Array<SpeObject>[][] voxels;
	int voxelArea = 5;

	private Type type = Type.TiledMap;

	// Dynamic Object array
	private Array<SpeDynamicObject> dynamicObjects;
	private Array<SpeEffectModule> effects;

	// Tracking arrays
	private Array<SpeContact> contacts;

	public Array<SpeContact> getContacts() {
		return contacts;
	}

	// Debug variables
	public boolean debugObjects = true;
	public boolean debugRays = true;
	public boolean debugVoxels = true;
	private ShapeRenderer sr;
	private BitmapFont bf;
	private Vector2 rayStart;
	private Vector2 rayEnd;
	private SpriteBatch sb;

	private Vector2 gravity;

	/**
	 * Creates the platform engine
	 */
	public SimplePlatformEngine(OrthographicCamera cam) {
		if (debugObjects || debugRays || debugVoxels) {
			bf = new BitmapFont();
			hudCam = new OrthographicCamera();
			hudCam.setToOrtho(false, Gdx.graphics.getWidth(),
					Gdx.graphics.getHeight());
			sr = new ShapeRenderer();
			sb = new SpriteBatch();
		}
		
		effects = new Array<SpeEffectModule>();
		
		this.cam = cam;
		gravity = new Vector2();
		dynamicObjects = new Array<SpeDynamicObject>();
		contacts = new Array<SpeContact>();
		lastTime = System.currentTimeMillis();
	}

	public void clearTrackingArrays() {
		contacts.clear();
	}

	/**
	 * Sets the strength of gravity straight down.
	 * 
	 * @param strength
	 *            Acceleration due to gravity in world units/second.
	 */
	public void setGravity(float strength) {
		gravity.y = -strength;
	}

	public void setGravity(float xStr, float yStr) {
		gravity.y = -yStr;
		gravity.x = xStr;
	}

	/**
	 * Creates a new map and makes it the current one. The map name should be
	 * the name and path of the tmx file. For instance, "data/levels/level1" THE
	 * TEXTURE ATLAS GENERATED BY THE PACKING PROCESS SHOULD BE IN THE SAME
	 * DIRECTORY AS THE .TMX FILE (e.g. data/levels)
	 * 
	 * @param mapName
	 *            The name and path of the map.
	 * @return The map that was created.
	 */
	public SpeMap createMap(String mapName) {
		currentMap = new SpeMap(this);
		currentMap.setMap(mapName);
		currentMap.setCamera(cam);

		voxels = new Array[currentMap.map.width / voxelArea + 1][currentMap.map.height
				/ voxelArea + 1];
		for (int x = 0; x < voxels.length; x++) {
			for (int y = 0; y < voxels[0].length; y++) {
				voxels[x][y] = new Array<SpeObject>();
			}
		}

		return currentMap;

	}

	/**
	 * Draws the current map, if one is available.
	 */
	public void DrawCurrentMap() {
		if (currentMap != null)
			// currentMap.drawMap();
			if (debugObjects) {
				sr.setColor(Color.CYAN);
				sr.setProjectionMatrix(cam.combined);
				sr.begin(ShapeType.FilledRectangle);
				// Loop through all the layers of the map.
				for (int x = 0; x < currentMap.map.width; x++) {
					for (int y = 0; y < currentMap.map.height; y++) {
						if (currentMap.map.layers.get(0).tiles[y][x] != 0) {
							sr.filledRect(x, currentMap.map.height - y - 1, 1, 1);

						}
					}
				}
				sr.end();

				sr.begin(ShapeType.Rectangle);
				for (SpeDynamicObject dynObj : dynamicObjects) {
					sr.setColor(0, 1, 0, 1);
					sr.rect(dynObj.loc.x, dynObj.loc.y, dynObj.loc.width,
							dynObj.loc.height);
				}

				for(SpeEffectModule e : effects) {
					sr.setColor(1, 1, 0, 1);
					sr.rect(e.area.x, e.area.y, e.area.width, e.area.height);
				}
				
				sr.end();
			}

		if (debugVoxels) {
			sr.begin(ShapeType.Rectangle);
			for (int x = 0; x < voxels.length; x++) {
				for (int y = 0; y < voxels[0].length; y++) {
					sr.setColor(Color.ORANGE);
					sr.rect(x * voxelArea, y * voxelArea, voxelArea, voxelArea);

				}
			}

			sr.end();

			for (int x = 0; x < voxels.length; x++) {
				for (int y = 0; y < voxels[0].length; y++) {
					Vector3 temp = new Vector3(x * voxelArea + (voxelArea / 2),
							y * voxelArea + (voxelArea / 2), 0);
					cam.project(temp);
					sb.begin();
					bf.draw(sb, voxels[x][y].size + "", temp.x, temp.y);
					sb.end();
				}
			}

		}

	}

	public SpeDynamicObject addDynamicObject(SpeDynamicObject entity) {
		entity.setEngine(this);
		dynamicObjects.add(entity);
		return entity;
	}
	
	public SpeEffectModule addEffect(SpeEffectModule effect) {
		effect.setEngine(this);
		effects.add(effect);
		return effect;
	}

	public void update(float delta) {
		clearTrackingArrays();
		// Loop through all the dynamic objects and update them.
		for (SpeDynamicObject obj : dynamicObjects) {
			if (obj.isDisposed())
				dynamicObjects.removeValue(obj, true);
			else
				obj.update(delta);
		}

		int totalCollisions = 0;

		// Collision code
		// Step the collision iterator
		collisionIteration++;
		if (collisionIteration == 100)
			collisionIteration = 0;

		// Clear the contacts from last time.
		contacts.clear();
		// Loop through every dynamic object.
		for (SpeDynamicObject obj : dynamicObjects) {
			if (obj.allowContacts) {
				// Set the collision iteration so we know we have checked it
				// this
				// loop.
				obj.collisionIteration = collisionIteration;
				// Check this object against everything else in its own voxel.
				Array<SpeObject> thisVoxel = getVoxelDynamicObjects(
						obj.getvX(), obj.getvY());
				if (thisVoxel != null) {
					for (SpeObject obj2 : thisVoxel) {
						// Convert it to a dynamic object for testing
						SpeDynamicObject temp = (SpeDynamicObject) obj2;
						// Ignore it if it has already been tested.
						if (temp.collisionIteration != collisionIteration) {
							// If the rectangles overlap, create a collision and
							// send it
							// to the objects.
							if (obj.loc.overlaps(temp.loc)) {
								totalCollisions++;
								contacts.add(new SpeContact(obj, temp));
							}
						}
					}
				}
			}
		}
		//Look through our effects and resolve collisions
		for(SpeEffectModule eff: effects) {
			eff.update(delta);
			//if(getVoxelDynamicObjects((int) (eff.area.getCenter().x/voxelArea), (int)(eff.area.getCenter().y)/voxelArea )!=null)
			eff.checkHits(getVoxelDynamicObjects((int) (eff.area.getCenter().x/voxelArea), (int)(eff.area.getCenter().y)/voxelArea ));
			if(eff.disposeMe) {
				eff.dispose();
				effects.removeValue(eff, false);
			}
		}

		// Run through the contacts list and send each contact to the objects
		// involved.
		for (SpeContact c : contacts) {
			c.a.contact(c.b);
			c.b.contact(c.a);
		}

	}

	/**
	 * Returns the current map.
	 * 
	 * @return The current map.
	 */
	public SpeMap getCurrentMap() {
		return currentMap;
	}

	public Vector2 getGravity() {
		return gravity;
	}

	/**
	 * Adds an object to the specified voxel.
	 * 
	 * @param o
	 *            The object to be added
	 * @param x
	 *            The voxel X
	 * @param y
	 *            The voxel Y
	 */
	public void addToVoxel(SpeObject o, int x, int y) {
		if (x >= 0 && x < voxels.length && y >= 0 && y < voxels[0].length)
			voxels[x][y].add(o);
	}

	/**
	 * Removes an object to the specified voxel.
	 * 
	 * @param o
	 *            The object to be added
	 * @param x
	 *            The voxel X
	 * @param y
	 *            The voxel Y
	 */

	public void removeFromVoxel(SpeObject o, int x, int y) {
		if (x >= 0 && x < voxels.length && y >= 0 && y < voxels[0].length)
			voxels[x][y].removeValue(o, true);
	}

	public Array<SpeObject> getVoxelDynamicObjects(int x, int y) {
		if (x >= 0 && x < voxels.length && y >= 0 && y < voxels[0].length)
			return voxels[x][y];
		else
			return null;
	}

	/**
	 * Sometimes the voxel info gets screwed up when disposing of objects.
	 * Resets all voxel information. Can be processor intensive, so use
	 * sparingly.
	 */
	public void resetVoxels() {
		for (int x = 0; x < voxels.length; x++) {
			for (int y = 0; y < voxels[0].length; y++)
				voxels[x][y].clear();
		}
		for (SpeDynamicObject obj : dynamicObjects)
			obj.setInitialVoxelInfo();
	}

}
